#include "Camera.hpp"
#include <format>
#include <stdexcept>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>

static void GX_STDC OnDeviceOfflineCallbackFun(void *userpara)
{
  DahengCamera* ptr=static_cast<DahengCamera*>(userpara);
  ptr->funcOffline();
}

DahengCamera::DahengCamera(const std::string& sn)
  : SN(std::format("DH{}",sn))
{
  char* sndata=new char[sn.size()+1];
  sndata[sn.size()]='\0';
  sn.copy(sndata,sn.size());
  GX_OPEN_PARAM openparam;
  openparam.openMode=GX_OPEN_SN;
  openparam.pszContent=sndata;
  openparam.accessMode=GX_ACCESS_EXCLUSIVE;

  GX_STATUS status=GXOpenDevice(&openparam,&handle);
  if(status!=GX_STATUS_SUCCESS)
    throw std::runtime_error(std::format("DahengCamera can't open device {}",status));

  delete [] sndata;

  GXRegisterDeviceOfflineCallback(handle,this,OnDeviceOfflineCallbackFun,&hOffline);

  config=new DahengConfig(handle);
}

DahengCamera::~DahengCamera()
{
  GXUnregisterDeviceOfflineCallback(handle,hOffline);
  GXCloseDevice(handle);
}

std::string DahengCamera::GetUUID() {
  return SN;
}

BaseConfig* DahengCamera::Config()
{
  return dynamic_cast<BaseConfig*>(config);
}

bool DahengCamera::ShotStatus()
{
  return ShotThread.joinable();
}

void DahengCamera::StartShot()
{
  auto imginf=ShotImageType();
  imageSize=imginf.size;
  imageData=new char[imginf.size];

  GX_STATUS status=GXStreamOn(handle);
  if(status!=GX_STATUS_SUCCESS)
    throw std::runtime_error(fmt::format("相机无法开始采集 err:{}",(int)status));
  ShotThread=std::jthread([this](std::stop_token stoptoken){
#define THREAD_EXIT_HANDLE(stat) \
  { ImageThreadExit(stat); return; }

    nlohmann::json AdditionalInf;
    AdditionalInf["Timestamp"]=0;
    AdditionalInf["BufferID"]=0;
    GX_STATUS status;
    while(!stoptoken.stop_requested())
    {
      PGX_FRAME_BUFFER frameBuffer[10];
      uint32_t frameCounter;
      while(true)
      {
        status=GXDQAllBufs(handle,frameBuffer,10,&frameCounter,2000);
        if(stoptoken.stop_requested()) return;
        if(status==GX_STATUS_TIMEOUT)
          continue; ///< 硬件触发无信号输入会超时
        if(status!=GX_STATUS_SUCCESS)
        {
          funcCameraMessage("DahengCamera GXDQAllBufs failed! ShotThread exited");
          spdlog::error(std::format("DahengCamera GXDQAllBufs failed! ShotThread exited {}",status));
          return;
        }
        else
          break;
      }

      for(uint32_t i=0;i<frameCounter;i++)
      {
        if(frameBuffer[i]!=nullptr)
        {
          if(frameBuffer[i]->nStatus==GX_FRAME_STATUS_SUCCESS)
          {
            memcpy(imageData,frameBuffer[i]->pImgBuf,imageSize);
            AdditionalInf["Timestamp"]=frameBuffer[i]->nTimestamp;
            AdditionalInf["BufferID"]=frameBuffer[i]->nBufID;
            funcNewImage(frameBuffer[i]->nFrameID,imageData,AdditionalInf.dump());
          }
          else {
            std::string inf=fmt::format("采集图像帧失败：ID:{} Status:{} Err:{}",(long)(frameBuffer[i]->nFrameID),(long)(frameBuffer[i]->nStatus),(int)frameBuffer[i]->nStatus);
            funcCameraMessage(inf);
            spdlog::warn(inf);
          }
        }
      }
      status=GXQAllBufs(handle);
      if(status!=GX_STATUS_SUCCESS)
      {
        funcCameraMessage("DahengCamera GXQAllBufs failed! ShotThread exited");
        spdlog::error(std::format("DahengCamera GXQAllBufs failed! ShotThread exited {}",status));
        return;
      }
    }

#undef THREAD_EXIT_HANDLE
  });
}

void DahengCamera::StopShot()
{
  if(ShotThread.joinable())
  {
    ShotThread.request_stop();
    ShotThread.join();
    GXStreamOff(handle);
    delete [] imageData;
  }
}

BaseCamera::ImgInf DahengCamera::ShotImageType()
{
  BaseCamera::ImgInf img;
  config->Get(3,(long&)(img.width));
  config->Get(2,(long&)(img.height));
  img.type=ImageType::GREY;
  img.size=img.width*img.height;
  return img;
}

long DahengCamera::MaxSingleImgSize()
{
  long width,height;
  config->Get(0,height);
  config->Get(1,width);
  return width*height;
}

float DahengCamera::FPS()
{
  double res;
  GXGetFloat(handle,GX_FLOAT_CURRENT_ACQUISITION_FRAME_RATE,&res);
  return float(res);
}
